﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Collections.ObjectModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.ServiceModel.Syndication;
using System.ServiceModel.Description;
using System.Runtime.Serialization;
using System.Xml;
using System.IO;
using System.ServiceModel.Activation;
using System.Security.Permissions;
using dasBlog.Storage.Configuration;
using System.Configuration;
using System.ServiceModel.Configuration;
#if BIZTALKSERVICES
using System.ServiceBus;
#endif

namespace dasBlog.Storage
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public sealed class StorageBus : IStorageBus, IStorageBusLocal, IStorageNode, IStreamStorageNode
    {
        Dictionary<string, ScopeDescription> storageScopes = new Dictionary<string, ScopeDescription>();
        Dictionary<PathSegmentName, List<NodeDescription>> storageNodesByNodeName = new Dictionary<PathSegmentName, List<NodeDescription>>();

        private static StorageBus _singletonInstance;
        public static StorageBus Current
        {
            get
            {
                if (_singletonInstance == null)
                {
                    _singletonInstance = new StorageBus();
                }
                return _singletonInstance;
            }
        }

        private StorageBus()
        {
            InitializeFromConfig();
        }

        private void InitializeFromConfig()
        {
            UserNameCredentialsSection credentialsSection = ConfigurationManager.GetSection("dasBlog.Storage/userNameCredentials") as UserNameCredentialsSection;
            UriLayoutsSection layoutsSection = ConfigurationManager.GetSection("dasBlog.Storage/uriLayouts") as UriLayoutsSection;
            NodesSection nodesSection = ConfigurationManager.GetSection("dasBlog.Storage/nodes") as NodesSection;
            StorageBusSection storageBusSection = ConfigurationManager.GetSection("dasBlog.Storage/storageBus") as StorageBusSection;
            ClientSection wcfClientSection = ConfigurationManager.GetSection("system.serviceModel/clients") as ClientSection;

            foreach (ScopeElement scope in storageBusSection.Scopes)
            {
                if (storageScopes.ContainsKey(scope.Name))
                {
                    throw new ConfigurationErrorsException(
                        String.Format("Duplicate scope '{0}'",
                                      scope.Name),
                        scope.ElementInformation.Source,
                        scope.ElementInformation.LineNumber);
                }
                else
                {
                    Dictionary<string, NodeDescription> usedNodes = new Dictionary<string, NodeDescription>();
                    UriLayoutSection layout = layoutsSection.UriLayouts.Get(scope.UriLayout);
                    if (layout != null)
                    {
                        ScopeDescription scopeDescription = new ScopeDescription(scope.Name);
                        scopeDescription.Paths = BuildPathMappings(credentialsSection,
                                                                   nodesSection,
                                                                   wcfClientSection,
                                                                   usedNodes,
                                                                   layout.Segments);
                        storageScopes.Add(scope.Name, scopeDescription);
                    }
                    else
                    {
                        throw new ConfigurationErrorsException(
                            String.Format("Cannot find UriLayout '{0}' for scope '{1}'",
                                          scope.UriLayout, scope.Name),
                            scope.ElementInformation.Source,
                            scope.ElementInformation.LineNumber);
                    }
                }
            }
        }

        private static PathMappings BuildPathMappings(UserNameCredentialsSection credentialsSection, NodesSection nodesSection, ClientSection wcfClientSection, Dictionary<string, NodeDescription> usedNodes, SegmentElementCollection segments)
        {
            PathMappings pathMappings = new PathMappings();
            foreach (SegmentElement segment in segments)
            {
                NodeDescription nodeDescription = null;
                if (usedNodes.ContainsKey(segment.Node))
                {
                    nodeDescription = usedNodes[segment.Node];
                    pathMappings.Add(new PathMapping(new PathSegmentName(segment.Role), nodeDescription));
                }
                else
                {
                    NodeElement node = nodesSection.Nodes.Get(segment.Node);
                    if (node != null)
                    {
                        if (node.Provider != null)
                        {
                            Type providerType = Type.GetType(node.Provider);
                            if (providerType != null)
                            {
                                IStorageNode nodeInstance = StorageNodeFactory.CreateStorageNode(providerType, node.InitData);
                                nodeDescription = new NodeDescription(nodeInstance);
                                usedNodes.Add(node.Name, nodeDescription);
                                if (segment.Segments.Count > 0)
                                {
                                    nodeDescription.Paths =
                                         BuildPathMappings(credentialsSection, nodesSection, wcfClientSection,
                                         usedNodes, segment.Segments);
                                }
                            }
                            else
                            {
                                throw new ConfigurationErrorsException(
                                    String.Format("Could not load provider type '{0}' for storage node '{1}'",
                                                  node.Provider, node.Name),
                                    node.ElementInformation.Source,
                                    node.ElementInformation.LineNumber);
                            }
                        }
                        else if (node.Client != null)
                        {
                            UserNameCredentialElement credential = null;
                            if (node.ClientCredential != null)
                            {
                                credential = credentialsSection.UserNameCredentials.Get(node.ClientCredential);
                            }
                            if (wcfClientSection.Endpoints.ContainsKey(node.Client))
                            {
                                ChannelEndpointElement endpointElement = wcfClientSection.Endpoints[node.Client];
                                Type contractType = Type.GetType(endpointElement.Contract);
                                if (contractType is IStreamStorageNode)
                                {
                                    ChannelFactory<IStorageNodeChannel> factory = new ChannelFactory<IStorageNodeChannel>(endpointElement.Name);
                                    factory.Endpoint.Behaviors.Add(new SimpleAuthenticationBehavior());
                                    if (credential != null)
                                    {
                                        factory.Credentials.UserName.UserName = credential.Username;
                                        factory.Credentials.UserName.Password = credential.Password;
                                    }
                                    nodeDescription = new NodeDescription(factory.CreateChannel());
                                    usedNodes.Add(node.Name, nodeDescription);
                                    if ( segment.Segments.Count > 0 )
                                    {
                                        nodeDescription.Paths = 
                                            BuildPathMappings(credentialsSection,nodesSection, wcfClientSection,
                                            usedNodes, segment.Segments);
                                    }
                                }
                                else if (contractType is IStorageNode)
                                {
                                    ChannelFactory<IStreamStorageNodeChannel> factory = new ChannelFactory<IStreamStorageNodeChannel>(endpointElement.Name);
                                    factory.Endpoint.Behaviors.Add(new SimpleAuthenticationBehavior());
                                    if (credential != null)
                                    {
                                        factory.Credentials.UserName.UserName = credential.Username;
                                        factory.Credentials.UserName.Password = credential.Password;
                                    }
                                    nodeDescription = new NodeDescription(factory.CreateChannel());
                                    usedNodes.Add(node.Name, nodeDescription);
                                    if ( segment.Segments.Count > 0 )
                                    {
                                        nodeDescription.Paths = 
                                            BuildPathMappings(credentialsSection,nodesSection, wcfClientSection,
                                            usedNodes, segment.Segments);
                                    }
                                }
                                else
                                {
                                    throw new ConfigurationErrorsException(
                                        String.Format("Invalid contract type '{0}' for client endpoint '{1}' referenced by storage node '{2}'",
                                                      endpointElement.Contract, endpointElement.Name, node.Name),
                                        node.ElementInformation.Source,
                                        node.ElementInformation.LineNumber);
                                }
                            }
                            else
                            {
                                throw new ConfigurationErrorsException(
                                        String.Format("Cannot find system.serviceModel/clients/endpoint named '{0}' for storage node '{1}'",
                                                      node.Client, node.Name),
                                        node.ElementInformation.Source,
                                        node.ElementInformation.LineNumber);
                            }
                        }
                        if (nodeDescription != null)
                        {
                            pathMappings.Add(new PathMapping(new PathSegmentName(segment.Role), nodeDescription));
                        }
                        else
                        {
                            throw new ConfigurationErrorsException(
                                        String.Format("No or invalid client reference or provider reference specified for storage node '{1}'",
                                                      node.Name),
                                        node.ElementInformation.Source,
                                        node.ElementInformation.LineNumber);
                        }
                    }
                    else
                    {
                        throw new ConfigurationErrorsException(
                                        String.Format("Cannot find node '{0}' for segment '{1}'",
                                                      segment.Node, segment.Role),
                                        node.ElementInformation.Source,
                                        node.ElementInformation.LineNumber);
                    }
                }
            }
            return pathMappings;
        }

        public IStorageNode FindNode(Moniker moniker)
        {
            var nodeDesc = ((IStorageBus)this).FindNode(moniker);
            if (nodeDesc != null)
            {
                return nodeDesc.GetStorageNode();
            }
            return null;
        }

        NodeDescription IStorageBus.FindNode(Moniker moniker)
        {
            if (moniker.Segments.Count > 0)
            {
                if (storageScopes.ContainsKey(moniker.Scope))
                {
                    NodeDescription node = null;
                    ScopeDescription scope = storageScopes[moniker.Scope];
                    PathMappings mappings = scope.Paths;
                    for (int i = 0; i < moniker.Segments.Count; i++)
                    {
                        PathMapping mapping = mappings[moniker.Segments[i].Name];
                        if (mapping == null)
                            return null;

                        node = mapping.Node;
                        mappings = node.Paths;
                    }
                    return node;
                }
            }
            return null;
        }

        public void AddStorageScope(ScopeDescription scopeDesc)
        {
            if (!storageScopes.ContainsKey(scopeDesc.Name))
            {
                storageScopes.Add(scopeDesc.Name, scopeDesc);
                foreach (PathMapping mapping in scopeDesc.Paths)
                {
                    mapping.Node.BindNode();
                }
            }
        }

        public List<ScopeDescription> GetScopes()
        {
            return new List<ScopeDescription>(storageScopes.Values);
        }

        public void RemoveStorageScope(string scopeName)
        {
            if (storageScopes.ContainsKey(scopeName))
            {
                storageScopes.Remove(scopeName);
            }
        }

        Message IStorageNode.GetItemSchema(string baseUri, string schemaId)
        {
            throw new NotImplementedException();
        }

        Message IStorageNode.Select(string baseUri, Moniker moniker, MonikerMatch match, QueryDescription query, SerializationMode serializationMode)
        {
            IStorageNode node = this.FindNode(moniker);
            return node.Select(baseUri, moniker, match, query, serializationMode);
        }

        Message IStorageNode.Aggregate(string baseUri, Moniker moniker, MonikerMatch match, string propertyName, SerializationMode serializationMode)
        {
            IStorageNode node = this.FindNode(moniker);
            return node.Aggregate(baseUri,moniker,match,propertyName,serializationMode);
        }

        Message IStorageNode.Get(string baseUri, Moniker itemId, SerializationMode serializationMode)
        {
            IStorageNode node = this.FindNode(itemId);
            return node.Get(baseUri, itemId, serializationMode);
        }

        void IStorageNode.Delete(Moniker itemId)
        {
            IStorageNode node = this.FindNode(itemId);
            node.Delete(itemId);
        }

        Moniker IStorageNode.Store(Moniker moniker, XmlElement item, SerializationMode serializationMode)
        {
            IStorageNode node = this.FindNode(moniker);
            return node.Store(moniker, item, serializationMode);
        }

        StreamStorageGetReply IStreamStorageNode.GetStream(StreamStorageGetRequest getRequest)
        {
            IStreamStorageNode node = this.FindNode(getRequest.itemId) as IStreamStorageNode;
            return node.GetStream(getRequest);
        }

        StreamStorageSetReply IStreamStorageNode.SetStream(StreamStorageSetRequest addRequest)
        {
            IStreamStorageNode node = this.FindNode(addRequest.moniker) as IStreamStorageNode;
            return node.SetStream(addRequest);
        }
    }

    public class StorageBusFactory : ServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            if (serviceType == typeof(StorageBus))
            {
                var host = new ServiceHost(StorageBus.Current, baseAddresses);

                host.Opening += delegate(object sender, EventArgs e)
                {
                    foreach (var ep in host.Description.Endpoints)
                    {
                        ep.Behaviors.Add(new SimpleAuthenticationBehavior());
                    }
                };
                return host;
            }
            else
            {
                throw new CommunicationException();
            }
        }
    }
}
